#include <SDL.h>

#include <assert.h>

#include "anim.h"
#include "ship.h"
#include "direction.h"
#include "view.h"
#include "misc.h"

extern View g_view;
extern const int g_center_ship_offset;

const int rotate_delta_ticks = 50;
const int move_delta_ticks = 10;

int UpdateAnimRotateShip(Anim* anim, int delta_ticks);
int UpdateAnimMoveShip(Anim* anim, int delta_ticks);
int UpdateAnimSequence(Anim* anim, int delta_ticks);
int UpdateAnimParallel(Anim* anim, int delta_ticks);

Anim* InitAnimRotateShip(Ship* ship, int direction_end) {
    Anim *anim = calloc(1, sizeof(Anim));

    anim->type = ANIM_ROTATE_SHIP;
    anim->anim.rotate_ship.ship = ship;
    anim->anim.rotate_ship.start_direction = ship->direction;

    int n_direction_cw = 0;
    int n_direction_ccw = 0;

    if (ship->direction < direction_end) {
        n_direction_cw = direction_end - ship->direction;
        n_direction_ccw = ship->direction + DIR_MAX - direction_end;
    } else {
        n_direction_cw = DIR_MAX - ship->direction + direction_end;
        n_direction_ccw = ship->direction - direction_end;
    }

    if (n_direction_cw > n_direction_ccw) {
        anim->anim.rotate_ship.n_direction = n_direction_ccw;
        anim->anim.rotate_ship.clockwise = 0;
    } else {
        anim->anim.rotate_ship.n_direction = n_direction_cw;
        anim->anim.rotate_ship.clockwise = 1;
    }

    anim->anim.rotate_ship.time = 0;

    return anim;
}

Anim* InitAnimMoveShip(Ship* ship, int direction, int offset) {
    Anim *anim = calloc(1, sizeof(Anim));

    anim->type = ANIM_MOVE_SHIP;
    anim->anim.move_ship.ship = ship;
    anim->anim.move_ship.x_start = -1;
    anim->anim.move_ship.y_start = -1;
    anim->anim.move_ship.x_offset_start = 0;
    anim->anim.move_ship.y_offset_start = 0;
    anim->anim.move_ship.len_end = (g_center_ship_offset * 2);
    anim->anim.move_ship.len = 0;
    anim->anim.move_ship.time = 0;

    return anim;
}

Anim* InitAnimSequence(Anim* anim_first, Anim* anim_second) {
    Anim *anim = calloc(1, sizeof(Anim));

    anim->type = ANIM_SEQUENCE;
    anim->anim.sequence.first = anim_first;
    anim->anim.sequence.second = anim_second;

    return anim;
}

Anim* InitAnimParallel(Anim* anim_first, Anim* anim_second) {
    Anim *anim = calloc(1, sizeof(Anim));

    anim->type = ANIM_PARALLEL;
    anim->anim.parallel.first = anim_first;
    anim->anim.parallel.second = anim_second;

    return anim;
}

int UpdateAnimMoveShip(Anim* anim, int delta_ticks) {
    AnimMoveShip* move_ship = &anim->anim.move_ship;
    Ship* ship = move_ship->ship;

    if (move_ship->x_start == -1 && move_ship->y_start == -1) {
        move_ship->x_start = ship->x;
        move_ship->y_start = ship->y;

        move_ship->x_offset_start = ship->x_offset;
        move_ship->y_offset_start = ship->y_offset;
    }

    move_ship->time += delta_ticks;
    int delta = 0;
    int res = 0;
    if (move_ship->time > move_ship->len_end * move_delta_ticks) {
        delta = move_ship->len_end;
        res = 1;
    } else {
        delta = move_ship->time / move_delta_ticks;
        res = 0;
    }

    ship->x = move_ship->x_start;
    ship->y = move_ship->y_start;
    switch (ship->direction) {
        case DIR_UP:
            ship->x_offset = move_ship->x_offset_start - delta;
            ship->y_offset = move_ship->y_offset_start + delta;
            break;
        case DIR_UP_RIGHT:
            ship->y_offset = move_ship->y_offset_start + delta;
            break;
        case DIR_RIGHT:
            ship->x_offset = move_ship->x_offset_start + delta;
            ship->y_offset = move_ship->y_offset_start + delta;
            break;
        case DIR_DOWN_RIGHT:
            ship->x_offset = move_ship->x_offset_start + delta;
            break;
        case DIR_DOWN:
            ship->x_offset = move_ship->x_offset_start + delta;
            ship->y_offset = move_ship->y_offset_start - delta;
            break;
        case DIR_DOWN_LEFT:
            ship->y_offset = move_ship->y_offset_start - delta;
            break;
        case DIR_LEFT:
            ship->x_offset = move_ship->x_offset_start - delta;
            ship->y_offset = move_ship->y_offset_start - delta;
            break;
        case DIR_UP_LEFT:
            ship->x_offset = move_ship->x_offset_start - delta;
            break;
        default:
            return 1;
    }
    NormShipOffset(ship);
    return res;
}

int UpdateAnimRotateShip(Anim* anim, int delta_ticks) {
    anim->anim.rotate_ship.time += delta_ticks;
    if (anim->anim.rotate_ship.time >
            anim->anim.rotate_ship.n_direction * rotate_delta_ticks ) {
        anim->anim.rotate_ship.ship->direction =
            anim->anim.rotate_ship.start_direction +
            (anim->anim.rotate_ship.clockwise ?
             anim->anim.rotate_ship.n_direction :
             - anim->anim.rotate_ship.n_direction);
        NormShipDirection(anim->anim.rotate_ship.ship);
        return 1;
    }
    if (anim->anim.rotate_ship.clockwise)
        anim->anim.rotate_ship.ship->direction =
            anim->anim.rotate_ship.start_direction +
            anim->anim.rotate_ship.time / rotate_delta_ticks;
    else
        anim->anim.rotate_ship.ship->direction =
            anim->anim.rotate_ship.start_direction -
            anim->anim.rotate_ship.time / rotate_delta_ticks;
    NormShipDirection(anim->anim.rotate_ship.ship);
    return 0;
}

int UpdateAnimSequence(Anim* anim, int delta_ticks) {
    if (anim->anim.sequence.first) {
        if (UpdateAnim(anim->anim.sequence.first, delta_ticks)) {
            DestroyAnim(&anim->anim.sequence.first);
        }
    } else {
        if (anim->anim.sequence.second)
            return UpdateAnim(anim->anim.sequence.second, delta_ticks);
    }
    return 0;
}

int UpdateAnimParallel(Anim* anim, int delta_ticks) {
    int res_first = 1;
    int res_second = 1;

    if (anim->anim.parallel.first) {
        res_first = UpdateAnim(anim->anim.parallel.first, delta_ticks);
        if (res_first) {
            DestroyAnim(&anim->anim.parallel.first);
        }
    }

    if (anim->anim.parallel.second) {
        res_second = UpdateAnim(anim->anim.parallel.second, delta_ticks);
        if (res_second) {
            DestroyAnim(&anim->anim.parallel.second);
        }
    }

    return MIN(res_first, res_second);
}

int UpdateAnim(Anim* anim, int delta_ticks) {
    switch (anim->type) {
        case ANIM_ROTATE_SHIP:
            return UpdateAnimRotateShip(anim, delta_ticks);
            break;
        case ANIM_MOVE_SHIP:
            return UpdateAnimMoveShip(anim, delta_ticks);
            break;
        case ANIM_SEQUENCE:
            return UpdateAnimSequence(anim, delta_ticks);
            break;
        case ANIM_PARALLEL:
            return UpdateAnimParallel(anim, delta_ticks);
            break;
    }
    return 1;
}

void DestroyAnim(Anim** anim) {
    if (*anim) {
        if ((*anim)->type == ANIM_SEQUENCE) {
            if ((*anim)->anim.sequence.first)
                DestroyAnim(&(*anim)->anim.sequence.first);
            if ((*anim)->anim.sequence.second)
                DestroyAnim(&(*anim)->anim.sequence.second);
        }

        if ((*anim)->type == ANIM_PARALLEL) {
            if ((*anim)->anim.parallel.first)
                DestroyAnim(&(*anim)->anim.parallel.first);
            if ((*anim)->anim.parallel.second)
                DestroyAnim(&(*anim)->anim.parallel.second);
        }
        free(*anim);
        *anim = NULL;
        assert(*anim == 0);
    }
}
